package com.young.trigger.handler;

import com.young.common.InetTool;
import com.young.core.enums.LockTypeEnum;
import com.young.core.manager.BrokerManager;
import com.young.core.manager.LockManager;
import com.young.trigger.event.BrokerEvent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.event.ApplicationStartedEvent;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextClosedEvent;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import java.util.List;

/**
 * @description：
 * @author: yangyahui01
 * @date: 11/28/24 2:59 PM
 */
@Component
public class BrokerHandler implements ApplicationListener<ApplicationEvent> {

    private static final Logger LOGGER = LoggerFactory.getLogger(BrokerHandler.class);

    private static final String MASTER_LOCK = "master";

    @Autowired
    private LockManager lockManager;

    @Autowired
    private BrokerManager brokerManager;

    @Autowired
    private ApplicationEventPublisher applicationEventPublisher;

    /**
     * ApplicationContext 启动完成后,初始化锁信息
     * ApplicationContext 关闭时,释放资源
     *
     * @param event
     */
    @Override
    public void onApplicationEvent(ApplicationEvent event) {
        try {
            if (event instanceof ApplicationStartedEvent) {
                lockManager.ensure(MASTER_LOCK);
                LOGGER.info("Broker - namespace初始化成功");
            } else if (event instanceof ContextClosedEvent) {

                BrokerContext.close();
                //从broker表中删除自己的信息
                brokerManager.unregister(InetTool.LOCAL_IP);
                //从lock表删除锁信息
                lockManager.unlock(MASTER_LOCK, InetTool.LOCAL_IP);

                //发布离任事件
                applicationEventPublisher.publishEvent(new BrokerEvent(LockTypeEnum.LEAVE));

                LOGGER.info("Broker - 节点取消成功,{}", InetTool.LOCAL_IP);
            }
        } catch (Exception e) {
            LOGGER.error("Broker - 核心事件处理失败,{}", event.getClass().getSimpleName(), e);
        }
    }

    /**
     * Broker节点活性保持，即间歇性的在数据库中更新自己的时间，标识活性
     * 目的是：
     * 1）发现并删除死亡的broker
     * 2）保持当前broker的活性，只有活着的broker才能被选举为master以及执行任务分配
     */
    @Scheduled(initialDelay = 5000, fixedDelay = 3 * 1000)
    public void keepAlive() {

        LOGGER.info("Broker节点活性保持");

        if (BrokerContext.isClosed()) {
            return;
        }

        try {
            //先删除过期的broker
            brokerManager.expire(3 * 2);
            //保持活性更新自己的时间戳
            boolean alive = brokerManager.keepAlive(InetTool.LOCAL_IP, InetTool.HOST_NAME);
            if (!alive) {
                LOGGER.error("Broker - keepAlive更新失败,{}", InetTool.LOCAL_IP);
            }
            //从缓存中查询所有的broker信息并保存到broker上下文
            List<String> brokers = brokerManager.getCache();
            BrokerContext.brokers(brokers);
        } catch (Exception e) {
            LOGGER.error("Broker - keepAlive失败", e);
        }

    }

    /**
     * 更新lock，即抢占master
     * 需要特别注意，local、master状态需要一并修改
     * 目的是：
     * 1）每隔一秒，进行master锁检测，用于发现master失效等，即使抢锁
     * 2）如果状态变化，发送通知
     */
    @Scheduled(initialDelay = 5000, fixedDelay = 1000)
    public synchronized void lock() {
        if (BrokerContext.isClosed()) {
            return;
        }

        try {
            LockTypeEnum lockTypeEnum = lockManager.lock(MASTER_LOCK, InetTool.LOCAL_IP, 3);

            if (BrokerContext.isLocal()) {
                LOGGER.info("Broker - 解除local模式,{}", InetTool.LOCAL_IP);
            }
            //立即将本地状态调整为非local模式
            BrokerContext.checkpoint(true);

            //抢锁成功，刷新一下状态
            if (lockTypeEnum == LockTypeEnum.PROMOTED) {

                BrokerContext.master(InetTool.LOCAL_IP);

                LOGGER.info("Broker - PROMOTED事件,{}", InetTool.LOCAL_IP);

                this.applicationEventPublisher.publishEvent(new BrokerEvent(LockTypeEnum.PROMOTED));

            }
            // 如果抢锁失败或者结果不明确
            else if (lockTypeEnum == LockTypeEnum.LEAVE || (BrokerContext.isMaster() && lockTypeEnum == LockTypeEnum.UNKNOWN)) {
                BrokerContext.master(null);
                this.applicationEventPublisher.publishEvent(new BrokerEvent(LockTypeEnum.LEAVE));
                LOGGER.info("Broker - LEAVE事件,{}", InetTool.LOCAL_IP);
            }
            return;
        } catch (Exception e) {
            LOGGER.error("Broker - 尝试lock失败", e);
        }
        LOGGER.info("Broker -（可能）进入local模式,{},过期剩余{}ms", InetTool.LOCAL_IP, BrokerContext.remaining());
        BrokerContext.checkpoint(false);
    }
}
